// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_job_coordinator.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "content/browser/service_worker/service_worker_register_job_base.h"

namespace content {

namespace {

    bool IsRegisterJob(const ServiceWorkerRegisterJobBase& job)
    {
        return job.GetType() == ServiceWorkerRegisterJobBase::REGISTRATION_JOB;
    }

}

ServiceWorkerJobCoordinator::JobQueue::JobQueue() = default;

ServiceWorkerJobCoordinator::JobQueue::JobQueue(JobQueue&&) = default;

ServiceWorkerJobCoordinator::JobQueue::~JobQueue()
{
    DCHECK(jobs_.empty()) << "Destroying JobQueue with " << jobs_.size()
                          << " unfinished jobs";
}

ServiceWorkerRegisterJobBase* ServiceWorkerJobCoordinator::JobQueue::Push(
    std::unique_ptr<ServiceWorkerRegisterJobBase> job)
{
    if (jobs_.empty()) {
        jobs_.push_back(std::move(job));
        StartOneJob();
    } else if (!job->Equals(jobs_.back().get())) {
        jobs_.push_back(std::move(job));
        DoomInstallingWorkerIfNeeded();
    }
    // Note we are releasing 'job' here in case neither of the two if() statements
    // above were true.

    DCHECK(!jobs_.empty());
    return jobs_.back().get();
}

void ServiceWorkerJobCoordinator::JobQueue::Pop(
    ServiceWorkerRegisterJobBase* job)
{
    DCHECK(job == jobs_.front().get());
    jobs_.pop_front();
    if (!jobs_.empty())
        StartOneJob();
}

void ServiceWorkerJobCoordinator::JobQueue::DoomInstallingWorkerIfNeeded()
{
    DCHECK(!jobs_.empty());
    if (!IsRegisterJob(*jobs_.front().get()))
        return;
    ServiceWorkerRegisterJob* job = static_cast<ServiceWorkerRegisterJob*>(jobs_.front().get());
    auto it = jobs_.begin();
    for (++it; it != jobs_.end(); ++it) {
        if (IsRegisterJob(**it)) {
            job->DoomInstallingWorker();
            return;
        }
    }
}

void ServiceWorkerJobCoordinator::JobQueue::StartOneJob()
{
    DCHECK(!jobs_.empty());
    jobs_.front()->Start();
    DoomInstallingWorkerIfNeeded();
}

void ServiceWorkerJobCoordinator::JobQueue::AbortAll()
{
    for (const auto& job : jobs_)
        job->Abort();
    jobs_.clear();
}

void ServiceWorkerJobCoordinator::JobQueue::ClearForShutdown()
{
    jobs_.clear();
}

ServiceWorkerJobCoordinator::ServiceWorkerJobCoordinator(
    base::WeakPtr<ServiceWorkerContextCore> context)
    : context_(context)
{
}

ServiceWorkerJobCoordinator::~ServiceWorkerJobCoordinator()
{
    if (!context_) {
        for (auto& job_pair : job_queues_)
            job_pair.second.ClearForShutdown();
        job_queues_.clear();
    }
    DCHECK(job_queues_.empty()) << "Destroying ServiceWorkerJobCoordinator with "
                                << job_queues_.size() << " job queues";
}

void ServiceWorkerJobCoordinator::Register(
    const GURL& pattern,
    const GURL& script_url,
    ServiceWorkerProviderHost* provider_host,
    const ServiceWorkerRegisterJob::RegistrationCallback& callback)
{
    std::unique_ptr<ServiceWorkerRegisterJobBase> job(
        new ServiceWorkerRegisterJob(context_, pattern, script_url));
    ServiceWorkerRegisterJob* queued_job = static_cast<ServiceWorkerRegisterJob*>(
        job_queues_[pattern].Push(std::move(job)));
    queued_job->AddCallback(callback, provider_host);
}

void ServiceWorkerJobCoordinator::Unregister(
    const GURL& pattern,
    const ServiceWorkerUnregisterJob::UnregistrationCallback& callback)
{
    std::unique_ptr<ServiceWorkerRegisterJobBase> job(
        new ServiceWorkerUnregisterJob(context_, pattern));
    ServiceWorkerUnregisterJob* queued_job = static_cast<ServiceWorkerUnregisterJob*>(
        job_queues_[pattern].Push(std::move(job)));
    queued_job->AddCallback(callback);
}

void ServiceWorkerJobCoordinator::Update(
    ServiceWorkerRegistration* registration,
    bool force_bypass_cache)
{
    DCHECK(registration);
    job_queues_[registration->pattern()].Push(
        base::WrapUnique<ServiceWorkerRegisterJobBase>(
            new ServiceWorkerRegisterJob(context_, registration,
                force_bypass_cache,
                false /* skip_script_comparison */)));
}

void ServiceWorkerJobCoordinator::Update(
    ServiceWorkerRegistration* registration,
    bool force_bypass_cache,
    bool skip_script_comparison,
    ServiceWorkerProviderHost* provider_host,
    const ServiceWorkerRegisterJob::RegistrationCallback& callback)
{
    DCHECK(registration);
    ServiceWorkerRegisterJob* queued_job = static_cast<ServiceWorkerRegisterJob*>(
        job_queues_[registration->pattern()].Push(
            base::WrapUnique<ServiceWorkerRegisterJobBase>(
                new ServiceWorkerRegisterJob(context_, registration,
                    force_bypass_cache,
                    skip_script_comparison))));
    queued_job->AddCallback(callback, provider_host);
}

void ServiceWorkerJobCoordinator::AbortAll()
{
    for (auto& job_pair : job_queues_)
        job_pair.second.AbortAll();
    job_queues_.clear();
}

void ServiceWorkerJobCoordinator::FinishJob(const GURL& pattern,
    ServiceWorkerRegisterJobBase* job)
{
    auto pending_jobs = job_queues_.find(pattern);
    DCHECK(pending_jobs != job_queues_.end()) << "Deleting non-existent job.";
    pending_jobs->second.Pop(job);
    if (pending_jobs->second.empty())
        job_queues_.erase(pending_jobs);
}

} // namespace content
